package affd.logic;

import java.util.ArrayList;

/**
 * A Tokenizer is a token builder which splits strings to Token arrays.
 * Tokenizer also provides a sentence builder which constructs sentences
 * from Token array.
 * 
 * @author Joteitti joteitti@cc.hut.fi
 *
 */
public class Tokenizer {
	
	public final String[] punctuations;
	public final String[] punctuationsEnd;
	
	/**
	 * Constructs a tokenizer. Parameters punctuation and punctuationEnd
	 * contains the punctuation data where the each punctuation mark is
	 * separated with a whitespace.
	 * 
	 * @param punctuations Punctuation data to be added.
	 * @param punctuationsEnd Punctuation end data to be added.
	 */
	public Tokenizer(String punctuation, String punctuationEnd) {
		// Parse parameters
		this.punctuations = punctuation.split(" ");
		this.punctuationsEnd = punctuationEnd.split(" ");
	}
	
	/**
	 * Constructs a list of tokens from the specified text.
	 * 
	 * @param text Text to be processed.
	 * @return List of the tokens.
	 */
	public ArrayList<Token> toTokens(String text) {
		ArrayList<Token> tokens = new ArrayList<Token>();
		int startIndex = 0;
		while(startIndex < text.length()) {
			// Handle white space
			if(Character.isWhitespace(text.charAt(startIndex))) {
				int endIndex = startIndex + 1;
				while(endIndex < text.length() && Character.isWhitespace(text.charAt(endIndex))) {
					endIndex++;
				}
				
				// Add new white space token
				tokens.add(new Token(text.substring(startIndex, endIndex), Token.Type.WHITE_SPACE));	
				startIndex = endIndex;
			}
			else {
				// Handle token
				int endIndex = startIndex + 1;
				while(endIndex < text.length() && !Character.isWhitespace(text.charAt(endIndex))) {
					endIndex++;
				}
				
				String token = text.substring(startIndex, endIndex);
				startIndex = endIndex;
				
				// Checks for word and punctuation tokens
				ArrayList<Token> punctuated = checkForWord(token);

				tokens.addAll(punctuated);
			}
		}
		
		return tokens;
	}
	
	/**
	 * Checks if the specified token contains word or punctuation and separates them.
	 * 
	 * @param token Token to be checked.
	 * @return List of the separated tokens.
	 */
	private ArrayList<Token> checkForWord(String token) {
		// Find punctuation
		String start = null;
		String end = null;
		for(String punctuation : punctuations) {
			if(token.length() > punctuation.length()) {
				if(token.startsWith(punctuation)) {
					start = punctuation;
				}
				if(token.endsWith(punctuation)) {
					end = punctuation;
				}
			}
		}
		
		// Check minimum length
		ArrayList<Token> tokens = new ArrayList<Token>();
		int minLength = (start != null ? start.length() : 0) + (end != null ? end.length() : 0);
		if(minLength != 0 && token.length() > minLength) {
			// Token may be punctuation or/and word
			int beginIndex = start != null ? start.length() : 0;
			int endIndex = end != null ? token.length() - end.length() : token.length();
			String word = token.substring(beginIndex, endIndex);
			
			// Check if the token type is word
			if(isWord(word)) {
				// Split the token
				if(start != null) {
					tokens.add(new Token(start, Token.Type.PUNCTUATION));
				}
				
				tokens.add(new Token(word, Token.Type.WORD));
				
				if(end != null) {
					tokens.add(new Token(end, Token.Type.PUNCTUATION));
				}
			}
			else {
				tokens.add(new Token(token, Token.Type.UNKNOWN));
			}
		}
		else {
			if(isWord(token)) {
				tokens.add(new Token(token, Token.Type.WORD));
			}
			else if(isPunctuation(token)) {
				tokens.add(new Token(token, Token.Type.PUNCTUATION));
			}
			else {
				tokens.add(new Token(token, Token.Type.UNKNOWN));	
			}
		}
		
		return tokens;
	}
	
	/**
	 * Tests if the specified token is word. This means the token
	 * contains only letters.
	 * 
	 * @param token Token to be tested.
	 * @return True if the specified token is word otherwise false.
	 */
	private boolean isWord(String token) {
		// Empty token is not word
		if(token.isEmpty()) {
			return false;
		}
		
		for(int i = 0; i < token.length(); i++) {
			if(!Character.isLetter(token.charAt(i))) {
				return false;
			}
		}
		
		return true;
	}
	
	/**
	 * Tests if the specified token is punctuation mark.
	 * 
	 * @param token Token to be tested.
	 * @return True if the specified token is punctuation mark otherwise false.
	 */
	private boolean isPunctuation(String token) {
		// Empty token is not word
		if(token.isEmpty()) {
			return false;
		}
		
		for(String punctuation : punctuations) {
			if(token.equals(punctuation)) {
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * Constructs a list of sentences from the specified list of tokens.
	 * 
	 * @param text Tokens to be processed.
	 * @return List of the sentences.
	 */
	public ArrayList<Sentence> toSentences(ArrayList<Token> tokens) {
		ArrayList<Sentence> sentences = new ArrayList<Sentence>();
		
		// Process tokens
		for(int i = 0; i < tokens.size(); i++) {
			Token current = tokens.get(i);
			Token next = (i + 1) < tokens.size() ? tokens.get(i + 1) : null;
			
			// Add to sentence
			if(sentences.isEmpty()) {
				sentences.add(new Sentence());
			}
			sentences.get(sentences.size() - 1).addToken(current);
			
			// Check for ending punctuation
			if(current.getType() == Token.Type.PUNCTUATION && isPunctuationEnd(current.getContent())) {
				sentences.add(new Sentence());
			}
			
			// Check for serial words
			if(next != null && (current.getType() == Token.Type.WORD || current.getType() == Token.Type.UNKNOWN) && (next.getType() == Token.Type.WORD || next.getType() == Token.Type.UNKNOWN)) {
				sentences.add(new Sentence());
			}
		}
		
		return sentences;
	}
	
	/**
	 * Tests if the specified token is phased out punctuation.
	 * 
	 * @param token Token to be tested.
	 * @return True if the specified token is phased out punctuation otherwise false.
	 */
	private boolean isPunctuationEnd(String token) {
		// Empty token is not word
		if(token.isEmpty()) {
			return false;
		}
		
		for(String punctuation : punctuationsEnd) {
			if(token.equals(punctuation)) {
				return true;
			}
		}
		
		return false;
	}
	
}
